﻿using System;
using System.Collections.Generic;
using UnityEngine;
using xuni;

namespace xres
{


    public class xRes
    {
        static xRes s_inst;

        /// <summary>
        /// 使用xRes任何api前必须调用
        /// </summary>
        public static void Initialize()
        {
            if (null != s_inst)
            {
                Debug.Log($"xRes has init");
                return;
            }

            s_inst = new xRes();
            ResPath.Initialize("Bundles", true);
            xBehaviour.Initialize();
        }


        ManifestLoader _manifestLoader;

        readonly Dictionary<string, BundleLoader> _bundlesDict = new Dictionary<string, BundleLoader>();

        readonly Dictionary<string, BundleAssetLoader> _assetsDict = new Dictionary<string, BundleAssetLoader>();

        readonly Dictionary<string, SceneLoader> _scenesDict = new Dictionary<string, SceneLoader>();



        public static bool IsManifestLoaded(out ManifestLoader loader)
        {
            if (null == s_inst)
            {
                loader = null;
                return false;
            }

            loader = s_inst._manifestLoader;

            if (null != s_inst._manifestLoader && s_inst._manifestLoader.IsLoadFinish())
                return true;

            return false;
        }

        /// <summary>
        /// 解压资源完毕后调用, 热更完毕后重新加载;
        /// </summary>
        /// <returns></returns>
        public static ManifestLoader InitManifest()
        {
            return s_inst._InitManifest();
        }

        ManifestLoader _InitManifest()
        {
            /*
            var manifestPath = ResPath.BuildExternalBundlePath(ResPath.TempStringBuilder, ResPath.PlatformName);
            if (!File.Exists(manifestPath))
            {
                Debug.Log($"manifest not exist");
                return null;
            }
            */

            if (null == _manifestLoader)
            {
                var manifestLoader = new ManifestLoader(ResPath.PlatformName);
                _manifestLoader = manifestLoader;
                _manifestLoader.Load();
            }
            else
            {
                Debug.Log($"manifest has init");
            }

            return _manifestLoader;
        }

        //todo: 热更完成时, 重新加载manifest, 并重载已加载的Bundle


        //++++++++++ Bundle

        public static AssetBundle GetBundle(string bundleName)
        {
            return s_inst._GetBundleLoader(bundleName).bundle;
        }
        BundleLoader _GetBundleLoader(string bundleName)
        {
            bundleName = ResPath.BundleNameWithExt(bundleName);

            if (_bundlesDict.TryGetValue(bundleName, out var loader))
            {
                if (!loader.IsLoadFinish())
                {
                    /*
                    Debug.LogError($"xRes: bundle load in async mode, and not load finish");
                    return null;
                    */
                    throw new Exception($"xRes: bundle load in async mode, and not load finish");
                }

                return loader;
            }

            loader = CreateBundleLoader(bundleName);
            loader.Load(false);
            return loader;
        }

        public static BundleLoader LoadBundle(string bundleName)
        {
            return s_inst._LoadBundle(bundleName);
        }
        BundleLoader _LoadBundle(string bundleName)
        {
            bundleName = ResPath.BundleNameWithExt(bundleName);

            if (_bundlesDict.TryGetValue(bundleName, out var loader))
            {
                Debug.Log($"xRes: bundle loader exist: {bundleName}");
                return loader;
            }

            loader = CreateBundleLoader(bundleName);
            loader.Load(true);
            return loader;
        }

        BundleLoader CreateBundleLoader(string bundleName)
        {
            var loader = new BundleLoader(bundleName);
            _bundlesDict.Add(bundleName, loader);

            var myDeps = _manifestLoader.manifest.GetDirectDependencies(bundleName);
            for (var i = 0; i < myDeps.Length; ++i)
            {
                var depBundleName = myDeps[i];
                if (!_bundlesDict.TryGetValue(depBundleName, out var depLoader))
                {
                    Debug.Log($"{bundleName}, dep bundle 1st load: {depBundleName}");
                    depLoader = new BundleLoader(depBundleName);
                    _bundlesDict.Add(depBundleName, depLoader);
                }

                //添加相互依赖关系
                if (!loader._myDepList.Contains(depLoader))
                {
                    loader._myDepList.Add(depLoader);
                    if (!depLoader._depMeList.Contains(loader))
                    {
                        depLoader._depMeList.Add(loader);
                    }
                }
            }

            return loader;
        }


        public static BundleLoader GetBundleLoader(string bundleName)
        {
            s_inst._IsBundleLoaded(bundleName, out var loader);
            return loader;
        }
        public static bool IsBundleLoaded(string bundleName, out BundleLoader loader)
        {
            return s_inst._IsBundleLoaded(bundleName, out loader);
        }
        bool _IsBundleLoaded(string bundleName, out BundleLoader loader)
        {
            bundleName = ResPath.BundleNameWithExt(bundleName);

            if (_bundlesDict.TryGetValue(bundleName, out loader))
            {
                return loader.IsLoadFinish();
            }
            return false;
        }

        //++++++++++


        //++++++++++ Asset

        /*
        public static UnityEngine.Object GetAsset(string bundleName, string assetPath, Type assetType)
        {
            return s_inst._GetAsset(bundleName, assetPath, assetType);
        }
        */
        public static T GetAsset<T>(string bundleName, string assetPath) where T : UnityEngine.Object
        {
            return (T)s_inst._GetAsset(bundleName, assetPath, typeof(T));
        }
        UnityEngine.Object _GetAsset(string bundleName, string assetPath, Type assetType)
        {
            bundleName = ResPath.BundleNameWithExt(bundleName);
            assetPath = ResPath.AssetPathNoExt(assetPath);

            var assetKey = xuni.StringUtils.Builder.Append(bundleName)
                .Append('+').Append(assetPath)
                .Append('+').Append(assetType.Name)
                .BuildString();

            if (_assetsDict.TryGetValue(assetKey, out var loader))
            {
                if (!loader.IsLoadFinish())
                {
                    Debug.LogError($"xRes: asset load in async mode, and not load finish");
                    return null;
                    //throw new Exception($"xRes: asset load in async mode, and not load finish");
                }
                return loader.asset;
            }

            var bundleLoader = _GetBundleLoader(bundleName);

            loader = CreateBundleAssetLoader(bundleLoader, assetPath, assetType, assetKey);
            loader.Load(false);

            return loader.asset;
        }


        public static BundleAssetLoader LoadAsset(string bundleName, string assetPath, Type assetType)
        {
            return s_inst._LoadAsset(bundleName, assetPath, assetType);
        }
        BundleAssetLoader _LoadAsset(string bundleName, string assetPath, Type assetType)
        {
            bundleName = ResPath.BundleNameWithExt(bundleName);
            assetPath = ResPath.AssetPathNoExt(assetPath);

            var assetKey = xuni.StringUtils.Builder.Append(bundleName)
                .Append('+').Append(assetPath)
                .Append('+').Append(assetType.Name)
                .BuildString();

            if (_assetsDict.TryGetValue(assetKey, out var loader))
            {
                Debug.Log($"xRes: asset loader exist: {assetKey}");
                return loader;
            }

            var bundleLoader = LoadBundle(bundleName);

            loader = CreateBundleAssetLoader(bundleLoader, assetPath, assetType, assetKey);
            loader.Load(true);

            return loader;
        }

        BundleAssetLoader CreateBundleAssetLoader(BundleLoader bundleLoader, string assetPath, Type assetType, string assetKey)
        {
            Debug.Log($"bundle asset 1st load: {bundleLoader._bundleName}: {assetPath}, {assetType.Name}");

            var loader = new BundleAssetLoader(bundleLoader, assetPath, assetType);
            bundleLoader._assetLoaderList.Add(loader);
            /*
            if (!bundleLoader._assetLoaderList.Contains(loader)) //bug: new出来的怎么可能contains???
                bundleLoader._assetLoaderList.Add(loader);
                */

            _assetsDict.Add(assetKey, loader);

            return loader;
        }

        public static bool IsAssetLoaded(string bundleName, string assetPath, Type assetType, out BundleAssetLoader loader)
        {
            return s_inst._IsAssetLoaded(bundleName, assetPath, assetType, out loader);
        }
        bool _IsAssetLoaded(string bundleName, string assetPath, Type assetType, out BundleAssetLoader loader)
        {
            var key = xuni.StringUtils.Builder.Append(bundleName)
                .Append('+').Append(assetPath)
                .Append('+').Append(assetType.Name)
                .BuildString();

            if (_assetsDict.TryGetValue(key, out loader))
            {
                return loader.IsLoadFinish();
            }
            return false;
        }

        //++++++++++



        //++++++++++ Scene

        public static SceneLoader LoadScene(string bundleName, string sceneName, bool addictive)
        {
            return s_inst._LoadScene(bundleName, sceneName, addictive);
        }
        SceneLoader _LoadScene(string bundleName, string sceneName, bool addictive)
        {
            bundleName = ResPath.BundleNameWithExt(bundleName);
            sceneName = ResPath.AssetPathNoExt(sceneName);

            if (_scenesDict.TryGetValue(sceneName, out var loader))
            {
                Debug.Log($"xRes: scene loader exist: {sceneName}");
                loader.Load(true);
                return loader;
            }

            var bundleLoader = LoadBundle(bundleName);

            loader = new SceneLoader(bundleLoader, sceneName, addictive);
            _scenesDict.Add(sceneName, loader);
            loader.Load(true);

            return loader;
        }


        //++++++++++ Unload

        public static void Unload(string bundleName)
        {
            s_inst._Unload(bundleName);
        }
        void _Unload(string bundleName)
        {
            bundleName = ResPath.BundleNameWithExt(bundleName);

            if (_bundlesDict.TryGetValue(bundleName, out var loader))
            {
                loader.Unload();
            }
            else
            {
                Debug.Log($"{bundleName} loader not exist");
            }
        }

        public static void UnloadUnused()
        {
            s_inst._UnloadUnused();
        }
        void _UnloadUnused()
        {
            var itr = _bundlesDict.GetEnumerator();
            while (itr.MoveNext())
            {
                var loader = itr.Current.Value;
                loader.Unload();
            }
        }

        //++++++++++



    } //end of class


}

